package com.itedge.infrastructure.service.taskdata.impl;

import javax.persistence.NoResultException;

import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.transaction.annotation.Transactional;

import com.itedge.infrastructure.service.domain.impl.AbstractJpaEntityService;
import com.itedge.infrastructure.service.taskdata.ITaskDataEntityService;
import com.itedge.infrastructure.taskdata.impl.AbstractTaskDataEntity;

/**
 * Abstract JPA implementation of ITaskDataService, providing some general methods.
 * 
 * @author jhe
 *
 * @param <T extends AbstractTaskData>
 */
public abstract class AbstractJpaTaskDataEntityService<T extends AbstractTaskDataEntity> 
	extends AbstractJpaEntityService<T> implements ITaskDataEntityService<T> {

    /**
	 *  Update version property, in case taskData for associated process task
	 *  was changed or created in meantime.
	 */
	private void updateVersionAndIdAttr(T taskData) {
		T attached = this.findTaskData(taskData);
        if (attached != null) {
        	taskData.setId(attached.getId());
        	taskData.setVersion(attached.getVersion());
        }
	}
	
	/**
	 * Find persisted task either by taskData instance id,
     * or by taskId, if to-be-saved (merged) taskData instance is new.
	 * 
	 * @param taskData
	 * @return task data entity which has either id or taskId (linked task instance id) set as taskData in parameter
	 */
	private T findTaskData(T taskData) {
		T attached = null;
		/* find persisted task either by taskData instance id,
		 * or by taskId, if to-be-saved (merged) taskData instance is new */
		if (taskData.getId() != null) {
			attached = this.findEntity(taskData.getId());
		} 
		if (taskData.getTaskId() != null && attached == null) {
			attached = this.findEntityByProcessTaskId(taskData.getTaskId());
		}
		return attached;
	}

	@Override
	@Transactional
	public T merge(T taskData) {
		T merged = null; 
		synchronized(this) {
			this.updateVersionAndIdAttr(taskData);
			merged = getEntityManager().merge(taskData);
		}
		getEntityManager().flush();
		return merged;
	}

	@Override
	@Transactional
	public boolean lock(String taskId, String lockedBy) {
		boolean isLocked = false;
		synchronized(this) {
			T attached = this.findEntityByProcessTaskId(taskId);
			if (attached != null) {	
				if (attached.getLockedBy() == null) {
					attached.setLockedBy(lockedBy);
					getEntityManager().merge(attached);
					isLocked = true;
				} else {
					if (lockedBy.equals(attached.getLockedBy())) {
						isLocked = true;
					} else {
						isLocked = false;
					}
				}
			} else {
				T taskData;
				try {
					taskData = getClassReference().newInstance();
					taskData.setLockedBy(lockedBy);
					taskData.setTaskId(taskId);
					getEntityManager().persist(taskData);
					isLocked = true;
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
		getEntityManager().flush();
		return isLocked;
	}

	@Override
	public T findEntityByProcessTaskId(String taskId) {
		T result = null;
        try {
            result = getEntityManager().createQuery("select o from "+getClassReference().getSimpleName()+" o where o.taskId = :taskId",
            		getClassReference()).setParameter("taskId", taskId).getSingleResult();
        } catch(EmptyResultDataAccessException e){
        	return null;
        } catch(NoResultException e) {
	        return null;
	    }
        return result;
	}

}
